package net.zoneland.zrdp.common.utils.ip;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import javax.servlet.http.HttpServletRequest;

import com.google.common.net.InetAddresses;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;

import net.zoneland.zrdp.common.utils.ServletUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 获取IP方法
 *
 * @author zonevue
 */
public class IpUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(IpUtils.class);
    // 匹配0-255
    public static final String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
    // 匹配 ipv4
    public static final String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
    // 匹配 ipv4通配符 *.*.*.*，xxx.*.*.*, xxx.xxx.*.*, xxx.xxx.xxx.*, xxx.*.xxx.* ...xxx.xxx.xxx.xxx
    public static final String REGX_IP_WILDCARD = "(((" + REGX_0_255 + "｜\\*)\\.){3}(" + REGX_0_255 + "|\\*))";
    // 匹配网段 xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx
    public static final String REGX_IP_SEG = "(" + REGX_IP + "-" + REGX_IP + ")";
    private static final Pattern IP_REGEX = Pattern.compile(REGX_IP);
    private static final Pattern IP_WILDCARD_REGEX = Pattern.compile(REGX_IP_WILDCARD);
    private static final Pattern IP_SEG_REGEX = Pattern.compile(REGX_IP_SEG);
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String UNKNOWN = "unknown";
    private static final int A_CLASS = 10;
    private static final int B_CLASS = 172;
    private static final int C_CLASS = 192;
    private static final int LOOPBACK = 127;
    private static final int B_CLASS_SUBNET_MIN = 16;
    private static final int B_CLASS_SUBNET_MAX = 31; // B类私有IP地址范围
    private static final int C_CLASS_SUBNET = 168; // C类私有IP地址
    private static final int IP_SPLIT_LENGTH = 4;
    private static final InetAddress LOCAL_HOST;

    static {
        LOCAL_HOST = initializeLocalHost();
    }

    // 使用数组列出所有可能包含客户端IP的头文件
    private static final String[] HEADERS = {
        "x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP", "X-Real-IP", "X-Forwarded-For"
    };

    private IpUtils() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 获取本机IP
     */
    private static InetAddress initializeLocalHost() {
        try {
            return InetAddress.getLocalHost();
        } catch (final UnknownHostException e) {
            // 记录错误日志
            LOGGER.error("获取ip失败：", e);
            return null;
        }
    }

    /**
     * 获取客户端IP
     *
     * @return IP地址
     */
    public static String getIpAddr() {
        return getIpAddr(ServletUtils.getRequest());
    }

    /**
     * 获取客户端IP
     *
     * @param request 请求对象
     * @return IP地址
     */
    public static String getIpAddr(final HttpServletRequest request) {
        if (request == null) {
            return UNKNOWN;
        }

        final String ip = request.getRemoteAddr();
        if (LOCALHOST_IPV6.equals(ip)) {
            return LOCALHOST_IPV4;
        }

        // 使用Stream API遍历头文件
        return Arrays.stream(HEADERS)
            .map(request::getHeader)
            .filter(IpUtils::isValidHeaderIp)
            .filter(IpUtils::isIP)
            .findFirst()
            .orElse(ip); // 如果没有找到有效的IP，则返回原始的RemoteAddr
    }

    /**
     * 检查是否为内部IP地址
     *
     * @param ip IP地址
     * @return 结果
     */
    public static boolean internalIp(final String ip) {
        return  LOCALHOST_IPV4.equals(ip) || internalIp(textToNumericFormatV4(ip));
    }


    /**
     * 检查是否为内部IP地址
     *
     * @param ip byte地址
     * @return 结果
     */
    private static boolean internalIp(final byte[] ip) {
        if (Objects.isNull(ip) || ip.length < IP_SPLIT_LENGTH) {
            return true;
        }
        switch (ip[0] & 0xFF) {
            case A_CLASS:
                // A类私有IP地址
                return true;
            case B_CLASS:
                // B类私有IP地址
                return (ip[1] & 0xFF) >= B_CLASS_SUBNET_MIN && (ip[1] & 0xFF) <= B_CLASS_SUBNET_MAX;
            case C_CLASS:
                // C类私有IP地址
                return (ip[1] & 0xFF) == C_CLASS_SUBNET;
            case LOOPBACK:
                // 本地回环地址
                return true;
            default:
                return false;
        }
    }

    /**
     * 将IPv4地址转换成字节
     *
     * @param ipAddress IPv4地址
     * @return byte 字节
     */
    public static byte[] textToNumericFormatV4(final String ipAddress) {
        try {
            final InetAddress address = InetAddress.getByName(ipAddress);
            return address.getAddress();
        } catch (final UnknownHostException e) {
            LOGGER.error("IPv4地址转换失败{}：", ipAddress, e);
            return new byte[0];
        }
    }

    /**
     * 获取IP地址
     *
     * @return 本地IP地址
     */
    public static String getHostIp() {
        if (LOCAL_HOST == null) {
            return LOCALHOST_IPV4;
        }
        return LOCAL_HOST.getHostAddress();
    }

    /**
     * 获取主机名
     *
     * @return 本地主机名
     */
    public static String getHostName() {
        if (LOCAL_HOST == null) {
            return UNKNOWN;
        }
        return LOCAL_HOST.getHostName();
    }


    /**
     * 检测给定字符串是否为有效IP地址
     *
     * @param headerIp 被检测的字符串
     * @return 是否有效IP地址
     */
    private static boolean isValidHeaderIp(String headerIp) {
        return StringUtils.isNotBlank(headerIp) && !UNKNOWN.equalsIgnoreCase(headerIp);
    }


    /**
     * 是否为IP
     * @param ip 字符串
     * @return 是否为IP
     */
    public static boolean isIP(final String ip) {
        return StringUtils.isNotBlank(ip) && IP_REGEX.matcher(ip).matches();
    }

    /**
     * 是否为IP，或 *为间隔的通配符地址
     * @param ip 字符串
     * @return 是否为IP,或 *为间隔的通配符地址
     */
    public static boolean isIpWildCard(final String ip) {
        return StringUtils.isNotBlank(ip) && IP_WILDCARD_REGEX.matcher(ip).matches();
    }

    /**
     * 检测参数是否在ip通配符里
     * @param ipWildCard ip通配符
     * @param ip ip
     * @return 检测参数ip是否在ip通配符里
     */
    private static boolean ipIsInWildCardNoCheck(final String ipWildCard, final String ip) {
        final String[] wildCardParts = StringUtils.split(ipWildCard, ".");
        final String[] ipParts = StringUtils.split(ip, ".");
        return IntStream.range(0, wildCardParts.length)
            .allMatch(i -> {
                final String wildCardPart = wildCardParts[i];
                return "*".equals(wildCardPart) || wildCardPart.equals(ipParts[i]);
            });
    }

    /**
     * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
     * @param ipSeg ip段字符串
     * @return 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
     */
    public static boolean isIPSegment(final String ipSeg) {
        return StringUtils.isNotBlank(ipSeg) && IP_SEG_REGEX.matcher(ipSeg).matches();
    }

    /**
     * 判断ip是否在指定网段中 （网段规则如：10.10.10.1-10.10.10.99)
     * @param ipArea ip段字符串
     * @param ip ip
     * @return ip是否在指定网段中
     */
    private static boolean ipIsInNetNoCheck(final String ipArea, final String ip) {
        final String[] ipAreaSplit = StringUtils.split(ipArea, "-");
        // 拆分IP地址段 取过滤网段的第一个ip
        final InetAddress startIpAddress = InetAddresses.forString(ipAreaSplit[0]);
        // 拆分IP地址段 取过滤网段的第二个ip
        final InetAddress endIpAddress = InetAddresses.forString(ipAreaSplit[1]);
        final InetAddress targeIpAddress = InetAddresses.forString(ip);
        // 转换为整型进行比较
        final int startIp = InetAddresses.coerceToInteger(startIpAddress);
        final int endIp = InetAddresses.coerceToInteger(endIpAddress);
        final int targetIp = InetAddresses.coerceToInteger(targeIpAddress);

        final Range<Integer> range = Range.of(startIp, endIp);
        return range.contains(targetIp);
    }

    /**
     * 校验ip是否符合过滤串规则
     *
     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` 多个过滤条件;号分隔
     * @param ip 校验IP地址
     * @return boolean 结果
     */
    public static boolean isMatchedIp(final String filter, final String ip) {
        if (StringUtils.isAnyEmpty(filter, ip)) {
            return false;
        }

        return Arrays.stream(StringUtils.split(filter, ";"))
            .map(String::trim)
            .anyMatch(iStr ->isMatchedIpByFilter(iStr, ip));
    }
    /**
     * 校验ip是否符合过滤串规则（单个过滤条件）
     *
     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
     * @param ip 校验IP地址
     * @return boolean 结果
     */
    private static boolean isMatchedIpByFilter(final String filter, final String ip) {
        boolean isExactIpMatch = isIP(filter) && filter.equals(ip);
        boolean isWildCardMatch = isIpWildCard(filter) && ipIsInWildCardNoCheck(filter, ip);
        boolean isIpSegmentMatch = isIPSegment(filter) && ipIsInNetNoCheck(filter, ip);

        return isExactIpMatch || isWildCardMatch || isIpSegmentMatch;
    }

}
